/*
 * Copyright 2011 group coding kata nantes
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.coding.kata.nantes.countwords.sylvain;

import org.apache.commons.lang.StringUtils;

import fr.coding.kata.nantes.countwords.commons.KataCountWordsInterface;

public class KataCountWords implements KataCountWordsInterface {

    protected String text;
    protected String word;
    protected boolean caseSensitive = false;

    @Override
    public KataCountWords withText(final String text) {
        this.text = text;
        return this;
    }

    @Override
    public KataCountWords withWord(final String word) {
        this.word = word;
        return this;
    }

    public KataCountWords caseSensitive() {
        this.caseSensitive = true;
        return this;
    }

    public KataCountWords caseInsensitive() {
        this.caseSensitive = false;
        return this;
    }

    @Override
    public Integer call() throws Exception {
        return StringUtils.isEmpty(text) | StringUtils.isEmpty(word) ? 0 : count();
    }

    private int count() {
        int count = 0;

        if (this.word.length() == 1) {
            count = countSingleChar();
        } else {
            count = countMultiChar();
        }

        return count;
    }

    private int countSingleChar() {
        int count = 0;

        final char search = toArray(this.word)[0];

        final int length = this.text.length();
        final char[] textChars = new char[length + 1];
        System.arraycopy(this.text.toCharArray(), 0, textChars, 0, length);
        textChars[length] = search; // append searched character to skip test on length

        int iText = 0;
        do {
            while (textChars[iText++] != search) {
                // skip uninteresting characters
            }
            count++;
        } while (iText <= length);

        return count - 1;
    }

    private int countMultiChar() {
        int count = 0;

        final char[] wordChars = toArray(this.word);
        final char[] textChars = toArray(this.text);

        final int textLength = textChars.length;
        final int wordLength = wordChars.length;

        final char firstChar = wordChars[0];
        final char lastChar = wordChars[wordLength - 1];

        int iText = 0;
        int iWord = 0;
        do {
            while (iText < textLength && textChars[iText++] != firstChar) {
                // skip uninteresting characters
            }

            // found a potential start of the word?
            if (iText <= textLength) {
                // move to next character in the word (sync with text)
                iWord++;
                // test each character of the word
                while (iWord < wordLength & iText < textLength && textChars[iText++] == wordChars[iWord++]) {
                    // the character matches
                }

                // check all characters of the word has been tested
                if (iWord == wordLength) {
                    /*
                     * check the last character is ok (an error can occurs if lengths are equal but
                     * the last character was ko)
                     */
                    if (textChars[iText - 1] == lastChar) {
                        count++;
                    } else {
                        // we are one step too far, rewind
                        iText--;
                    }
                }
            }

            // reset word iteration position
            iWord = 0;
        } while (iText < textLength);

        return count;
    }

    private char[] toArray(final String string) {
        return (caseSensitive ? string : string.toLowerCase()).toCharArray();
    }

}
